﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

namespace Nirvana
{
    /// <summary>
     /// 对象池管理：用于管理实体对象的引用计数，控制资源AB的加载/释放内存，避免对象频繁创建，销毁达到对象复用效果
     /// </summary>
    [DisallowMultipleComponent]
    public class GameObjectPool : UnitySingleton<GameObjectPool>
    {
        private static Logger logger = LogSystem.GetLogger("GameObjectPool");
        /// <summary>
        /// 保存以AB名加载的记录，以便下次直接加载
        /// </summary>
        private Dictionary<AssetID, GameObjectCache> mAssetIDMap = new Dictionary<AssetID, GameObjectCache>();
        /// <summary>
        /// 保存以Prefab资源加载的记录，以便下次直接加载
        /// </summary>
        private Dictionary<GameObject, GameObjectCache> mPrefabMap = new Dictionary<GameObject, GameObjectCache>();
        /// <summary>
        /// 记录被引用激活的缓存：不在对象池中的所有实体对象
        /// </summary>
        private Dictionary<int, GameObjectCache> mInstanceIDMap = new Dictionary<int, GameObjectCache>();

        private Transform root;
        private float curLoadTime;

        private float defaultReleaseAfterFree = 30f;
        private int defaultMaxPoolCount = 16;
        public float DefaultReleaseAfterFree
        {
            get
            {
                return defaultReleaseAfterFree;
            }
            set
            {
                defaultReleaseAfterFree = value;
                foreach (var p in mAssetIDMap)
                    p.Value.SetReleaseTimeAfterFree(value);
                foreach (var p in mPrefabMap)
                    p.Value.SetReleaseTimeAfterFree(value);
            }
        }
        public int DefaultInstancePoolCount
        {
            get
            {
                return this.defaultMaxPoolCount;
            }
            set
            {
                this.defaultMaxPoolCount = value;
                foreach (var p in this.mAssetIDMap)
                {
                    p.Value.SetPoolCount(value);
                }
                foreach (var p in this.mPrefabMap)
                {
                    p.Value.SetPoolCount(value);
                }
            }
        }

        public Dictionary<AssetID, GameObjectCache> AssetCahces => this.mAssetIDMap;
        public Dictionary<GameObject, GameObjectCache> ObjectCaches => this.mPrefabMap;

        /// <summary>
        /// 初始化对象池
        /// </summary>

        private void Awake()
        {
            this.root = transform;
            gameObject.SetActive(false);
            Scheduler.AddFrameListener(ClearFreeInPool);
        }

        /// <summary>
        /// 清除对象池中未使用的缓存
        /// </summary>
        private void ClearFreeInPool()
        {
            //每秒检测释放未引用的资源
            if (Time.time - curLoadTime >= 1f)
            {
                this.curLoadTime = Time.time;

                //检测所有记录
                this.mAssetIDMap.RemoveAll((assetid, cache) =>
                {
                    if (!string.IsNullOrEmpty(cache.Error)) return true;

                    cache.ClearOutTime();

                    //没有引用了，则清除Prefab资源
                    if (!cache.Loading && cache.CacheCount == 0 && cache.SpawnCount == 0)
                    {
                        cache.Unload();
                        return true;
                    }
                    return false;
                });

                this.mPrefabMap.RemoveAll((obj, cache) =>
                {
                    if (!string.IsNullOrEmpty(cache.Error)) return true;
                    cache.ClearOutTime();
                    if (!cache.Loading && cache.CacheCount == 0 && cache.SpawnCount == 0)
                    {
                        cache.Unload();
                        return true;
                    }
                    return false;
                });
            }
               

        }

        /// <summary>
        /// 以AB名方式加载对象
        /// </summary>
        /// <param name="assetID"></param>
        /// <returns></returns>
        public WaitLoadGameObject SpawnAsset1(AssetID assetID)
        {
            if (assetID.IsEmpty) return null;

            //检测是否已有加载记录
            if (!this.mAssetIDMap.TryGetValue(assetID, out GameObjectCache gameObjectCache))
            {
                gameObjectCache = new GameObjectCache(this.mInstanceIDMap);
                gameObjectCache.SetReleaseTimeAfterFree(this.defaultReleaseAfterFree);
                gameObjectCache.SetPoolCount(this.defaultMaxPoolCount);

                mAssetIDMap.Add(assetID, gameObjectCache);
                gameObjectCache.SpwanAsset(assetID);
            }
            gameObjectCache.Loading = true;
            return new WaitLoadGameObject(gameObjectCache);
        }

        public WaitLoadGameObject SpawnAsset2(AssetID assetID)
        {
            if (assetID.IsEmpty)
            {
                return null;
            }
            //检测是否已有加载记录
            if (!this.mAssetIDMap.TryGetValue(assetID, out GameObjectCache gameObjectCache))
            {
                gameObjectCache = new GameObjectCache(this.mInstanceIDMap);
                gameObjectCache.SetReleaseTimeAfterFree(this.defaultReleaseAfterFree);
                gameObjectCache.SetPoolCount(this.defaultMaxPoolCount);

                mAssetIDMap.Add(assetID, gameObjectCache);
                gameObjectCache.SpwanAsset(assetID);
            }

            gameObjectCache.Loading = true;
            var wait = PopWaitLoadCahce(gameObjectCache);
            return wait;
        }

        public WaitLoadGameObjectCache SpawnAsset(AssetID assetID)
        {
            return this.SpawnAssetWithQueue(assetID, null, 0);
        }
        public WaitLoadGameObjectCache SpawnAssetWithQueue(string bundle, string asset, InstantiateQueue instantiateQueue, int instantiatePriority)
        {
            return this.SpawnAssetWithQueue(new AssetID(bundle, asset), instantiateQueue, instantiatePriority);
        }
		        
        /// <summary>
        /// 以AB名方式加载对象
        /// </summary>
        /// <param name="assetID"></param>
        /// <returns></returns>
        public WaitLoadGameObjectCache SpawnAssetWithQueue(AssetID assetID, InstantiateQueue instantiateQueue, int instantiatePriority)
        {
           Assert.IsFalse(assetID.IsEmpty);
		               //检测是否已有加载记录
            GameObjectCache gameObjectCache;
            if (!this.mAssetIDMap.TryGetValue(assetID, out gameObjectCache))
            {
                gameObjectCache = new GameObjectCache(this.mInstanceIDMap);
                gameObjectCache.SetReleaseTimeAfterFree(this.defaultReleaseAfterFree);
                gameObjectCache.SetPoolCount(this.defaultMaxPoolCount);
                this.mAssetIDMap.Add(assetID, gameObjectCache);
                gameObjectCache.SpwanAsset(assetID);
            }
			gameObjectCache.Loading = true;
            return new WaitLoadGameObjectCache(gameObjectCache, instantiateQueue, instantiatePriority);
        }

        /// <summary>
        /// 以prefab资源加载对象
        /// </summary>
        /// <param name="prefab"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        public GameObject Spawn(GameObject prefab, Transform parent)
        {
            Assert.IsNotNull<GameObject>(prefab);
            //检测是否已有加载记录
            if (!this.mPrefabMap.TryGetValue(prefab, out GameObjectCache gameObjectCache))
            {
                gameObjectCache = new GameObjectCache(this.mInstanceIDMap);
                gameObjectCache.SetReleaseTimeAfterFree(this.defaultReleaseAfterFree);
                gameObjectCache.SetPoolCount(this.defaultMaxPoolCount);

                mPrefabMap.Add(prefab, gameObjectCache);
                gameObjectCache.SetPoolStrategyData(prefab);
            }
            return gameObjectCache.PoolGet(parent);
        }

        public T Spawn<T>(T prefab, Transform parent) where T : Component
        {
            Assert.IsNotNull<T>(prefab);
            GameObject gameObject = this.Spawn(prefab.gameObject, parent);
            T component = gameObject.GetComponent<T>();
            if (component == null)
            {
                Debug.LogFormat("Can not load prefab with componet: {0}", typeof(T).Name);
                return null;
            }
            return component;
        }

        public void SetDefaultReleaseAfterFree(AssetID assetID, int value)
        {
            if (this.mAssetIDMap.TryGetValue(assetID, out GameObjectCache gameObjectCache))
            {
                gameObjectCache.SetReleaseTimeAfterFree((float)value);
            }
        }

        public void Free(GameObject instance, bool isNoSave = false)
        {
            if (instance == null)
            {
                Debug.Log("Try to free a null GameObject.");
                return;
            }
            if (this.root == null)
            {
                UnityEngine.Object.Destroy(instance);
                return;
            }
            int instanceID = instance.GetInstanceID();
            //检测释放对象是否在激活的引用列表中中，不在。则抛弃
            if (!this.mInstanceIDMap.TryGetValue(instanceID, out GameObjectCache gameObjectCache))
            {
                Debug.LogFormat("Try to free an instance {0} :{1} not allocated by this pool.", instance.name, instanceID);
                UnityEngine.Object.Destroy(instance);
                return;
            }
            this.mInstanceIDMap.Remove(instanceID);
            //放回对象池，引用计数-1
            instance.transform.SetParent(this.root);
            instance.transform.localPosition = Vector3.zero;
            instance.SetActive(false);
            gameObjectCache.PoolCache(instance, isNoSave);
        }

        /// <summary>
        /// 清除引用记录和引用计数
        /// </summary>
        public void Free(int instanceId)
        {
            //检测释放对象是否在激活的引用列表中中，
            if (!this.mInstanceIDMap.TryGetValue(instanceId, out GameObjectCache gameObjectCache))
            {
                GameObjectPool.logger.LogWarning("Try to free an instanceId {0} not allocated by this pool.", instanceId);
                return;
            }
            this.mInstanceIDMap.Remove(instanceId);
            gameObjectCache.Free();
        }

        public void Clear()
        {
            foreach (var p in this.mAssetIDMap)
            {
                p.Value.Clear();
            }
            this.mAssetIDMap.Clear();
            //
            foreach (var p in this.mPrefabMap)
            {
                p.Value.Clear();
            }
            this.mPrefabMap.Clear();
            //
            this.mInstanceIDMap.Clear();
        }

        public void ClearAllUnused()
        {
            this.mAssetIDMap.RemoveAll((assetid, cache) => {
                if (!string.IsNullOrEmpty(cache.Error)) return true;
                cache.Clear();
                if (!cache.Loading && cache.CacheCount == 0 && cache.SpawnCount == 0)
                {
                    cache.Unload();
                    return true;
                }
                return false;
            });

            this.mPrefabMap.RemoveAll((obj, cache) => {
                if (!string.IsNullOrEmpty(cache.Error))
                {
                    return true;
                }
                cache.Clear();
                if (!cache.Loading && cache.CacheCount == 0 && cache.SpawnCount == 0)
                {
                    cache.Unload();
                    return true;
                }
                return false;
            });

            waitLoadPool.Clear();
        }

        #region 数据对象缓存
        //数据对象缓存池，避免大量New，造成大量GC
        private const int MaxCacheCount = 50;
        private Queue<WaitLoadGameObject> waitLoadPool = new Queue<WaitLoadGameObject>(MaxCacheCount);

        public WaitLoadGameObject PopWaitLoadCahce(GameObjectCache cache)
        {
            if (waitLoadPool.Count > 0)
            {
                var it = waitLoadPool.Dequeue();

                while (!it.Init(cache)) //由于数据对象复用，可能又被其它功能引用到了
                {
                    if (waitLoadPool.Count > 0)
                    {
                        it = waitLoadPool.Dequeue();
                    }
                    else
                    {
                        it = new WaitLoadGameObject(cache);
                        break;
                    }
                }
                return it;
            }
            else
            {
                return new WaitLoadGameObject(cache);
            }
        }

        public void PushWaitLoadCache(WaitLoadGameObject waitload)
        {
            waitload.Dispose();
            if (waitLoadPool.Count < MaxCacheCount)
                waitLoadPool.Enqueue(waitload);
        }

        #endregion

    }
}

